<?php

declare(strict_types=1);

namespace core;

class App
{
    /** 默认应用配置 */
    private const DEFAULT_CONFIG = [
        'defaultController'   => 'Home',
        'defaultMethod'       => 'index',
        'controllerNamespace' => 'app\\controller',
        'debug'               => false,
    ];

    /** 合并后的应用配置 */
    private array $config;

    /** 控制器类名 */
    private string $controller = '';

    /** 方法名 */
    private string $method = '';

    /** URL参数数组 */
    private array $params = [];

    /** 控制器实例 */
    private ?object $controllerObj = null;

    /** 请求 URI（用于测试） */
    private string $uri;

    /**
     * 构造函数：接收外部配置
     *
     * @param array $config 自定义配置数组，会覆盖默认配置
     * @param string|null $uri 可选：用于测试的 URI，若为 null 则使用 $_SERVER['REQUEST_URI']
     */
    public function __construct(array $config = [], ?string $uri = null)
    {
        $this->config = array_merge(self::DEFAULT_CONFIG, $config);
        $this->uri = $uri ?? ($_SERVER['REQUEST_URI'] ?? '/');
    }

    /**
     * 应用运行入口
     *
     * @return void
     * @throws \Throwable
     */
    public function run(): void
    {
        try {
            $this->initializeConstants();
            $this->parseUrl();
            $this->loadController();
            $this->invokeControllerMethod();
        } catch (\Throwable $th) {
            $this->handleError($th);
        }
    }

    /**
     * 初始化必要的常量
     *
     * @return void
     */
    private function initializeConstants(): void
    {
        if (!defined('ROOT_PATH')) {
            define('ROOT_PATH', dirname(__DIR__) . DIRECTORY_SEPARATOR);
        }
    }

    /**
     * 解析 URL 路径
     *
     * @return void
     * @throws \InvalidArgumentException
     */
    private function parseUrl(): void
    {
        $uri = rawurldecode($this->uri);

        // 添加URI长度限制，防止DoS攻击
        if (mb_strlen($uri, '8bit') > 1024) {
            throw new \InvalidArgumentException('URI too long');
        }

        // 防御空字节和路径穿越尝试
        if (
            strpos($uri, "\0") !== false ||
            preg_match('/\.\.(\\\|\/|$)/', $uri) !== 0 ||
            strpos($uri, '%2e%2e') !== false
        ) {  // 检查编码的".."
            throw new \InvalidArgumentException('Invalid URI: contains illegal characters');
        }

        $path = parse_url($uri, PHP_URL_PATH) ?: '/';
        $path = trim($path, '/');

        if ($path === '') {
            $this->setDefaultRoute();
            return;
        }

        $segments = explode('/', $path);

        // 过滤空段
        $segments = array_filter($segments, 'strlen');
        $segments = array_values($segments);

        $this->validateAndSetRoute($segments);
    }

    /**
     * 设置默认路由
     *
     * @return void
     */
    private function setDefaultRoute(): void
    {
        $this->controller = $this->config['controllerNamespace'] . '\\' . $this->config['defaultController'];
        $this->method     = $this->config['defaultMethod'];
        $this->params     = [];
    }

    /**
     * 验证并设置路由参数
     *
     * @param array $segments URL分段数组
     * @return void
     * @throws \InvalidArgumentException
     */
    private function validateAndSetRoute(array $segments): void
    {
        $controllerName = ucfirst($segments[0] ?? $this->config['defaultController']);
        $methodName     = $segments[1] ?? $this->config['defaultMethod'];

        // 验证控制器名格式
        if (!$this->isValidIdentifier($controllerName, true)) {
            throw new \InvalidArgumentException("无效的控制器名称: {$controllerName}");
        }

        // 验证方法名格式
        if (!$this->isValidIdentifier($methodName, false)) {
            throw new \InvalidArgumentException("无效的方法名称: {$methodName}");
        }

        $this->controller = $this->config['controllerNamespace'] . '\\' . $controllerName;
        $this->method     = $methodName;
        $this->params     = array_slice($segments, 2);
    }

    /**
     * 验证标识符（控制器名/方法名）是否合法
     *
     * @param string $identifier 要验证的标识符
     * @param bool $isController 是否为控制器名
     * @return bool
     */
    private function isValidIdentifier(string $identifier, bool $isController = true): bool
    {
        // 添加长度限制，防止长字符串攻击
        if (strlen($identifier) > 64) {
            return false;
        }

        // 控制器必须以字母开头，方法名可以以下划线开头
        $pattern = $isController ? '/^[A-Za-z][A-Za-z0-9_]*$/u' : '/^[A-Za-z_][A-Za-z0-9_]*$/u';

        // preg_match 在处理无效UTF-8时可能返回false，需处理
        return preg_match($pattern, $identifier) === 1;
    }

    /**
     * 加载控制器类
     *
     * @return void
     * @throws \InvalidArgumentException
     */
    private function loadController(): void
    {
        if (!class_exists($this->controller)) {
            throw new \InvalidArgumentException("控制器类不存在: {$this->controller}");
        }

        // 检查控制器是否继承自允许的基类
        // $reflection = new \ReflectionClass($this->controller);
        // $allowedBaseClass = 'app\controller\BaseController'; // 假设的基础控制器类
        // if (!$reflection->isSubclassOf($allowedBaseClass) && $this->controller !== $allowedBaseClass) {
        //     throw new \InvalidArgumentException("不允许访问的控制器: {$this->controller}");
        // }

        $this->controllerObj = new $this->controller();

        if (!method_exists($this->controllerObj, $this->method)) {
            throw new \InvalidArgumentException(
                "方法不存在: {$this->controller}::{$this->method}"
            );
        }
    }

    /**
     * 调用控制器方法
     *
     * @return void
     * @throws \ReflectionException
     * @throws \InvalidArgumentException
     */
    private function invokeControllerMethod(): void
    {
        $reflectionMethod = new \ReflectionMethod($this->controllerObj, $this->method);

        if (!$reflectionMethod->isPublic()) {
            throw new \InvalidArgumentException(
                "方法不可访问: {$this->controller}::{$this->method}"
            );
        }

        // 禁止调用魔术方法，防止意外行为或攻击
        if (strpos($this->method, '__') === 0) {
            throw new \InvalidArgumentException(
                "禁止调用魔术方法: {$this->controller}::{$this->method}"
            );
        }

        $args = $this->prepareMethodArguments($reflectionMethod);
        $reflectionMethod->invokeArgs($this->controllerObj, $args);
    }

    /**
     * 准备方法调用参数
     *
     * @param \ReflectionMethod $method 反射方法对象
     * @return array
     * @throws \InvalidArgumentException
     */
    private function prepareMethodArguments(\ReflectionMethod $method): array
    {
        $parameters = $method->getParameters();
        $args = [];

        foreach ($parameters as $index => $parameter) {
            $paramName  = $parameter->getName();
            $paramValue = $this->params[$index] ?? null;

            if ($paramValue !== null) {
                // 修复: 对参数进行基本的类型强制和清理，防止注入
                $args[] = $this->sanitizeArgument($paramValue, $parameter);
            } elseif ($parameter->isDefaultValueAvailable()) {
                $args[] = $parameter->getDefaultValue();
            } else {
                throw new \InvalidArgumentException(
                    "缺少必要参数: {$paramName} " .
                        "在方法 {$this->controller}::{$this->method} 中"
                );
            }
        }

        return $args;
    }

    /**
     * 修复: 新增方法，用于清理和类型化参数
     *
     * @param mixed $value 原始参数值
     * @param \ReflectionParameter $parameter 参数反射对象
     * @return mixed
     */
    private function sanitizeArgument(mixed $value, \ReflectionParameter $parameter): mixed
    {
        $type = $parameter->getType();

        // 如果参数没有类型，返回字符串并移除控制字符
        if ($type === null) {
            return is_string($value) ? $this->removeControlChars($value) : $value;
        }

        $typeName = $type->getName();

        switch ($typeName) {
            case 'int':
                return (int) $value;
            case 'float':
                return (float) $value;
            case 'bool':
                return filter_var($value, FILTER_VALIDATE_BOOLEAN);
            case 'string':
                return $this->removeControlChars((string) $value);
            case 'array':
                return is_array($value) ? $value : [$value];
            default:
                // 拒绝复杂类型参数，只允许基本类型
                throw new \InvalidArgumentException(
                    "不支持的参数类型: {$typeName} 用于参数 {$parameter->getName()}"
                );
        }
    }

    /**
     * 移除字符串中的控制字符
     *
     * @param string $string
     * @return string
     */
    private function removeControlChars(string $string): string
    {
        return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $string);
    }

    /**
     * 处理应用错误
     *
     * @param Throwable $exception 异常对象
     * @return void
     */
    private function handleError(\Throwable $exception): void
    {
        $statusCode = $exception instanceof \InvalidArgumentException ? 400 : 500;
        http_response_code($statusCode);

        // 日志记录
        $logMessage = "Application Error: " . $exception->getMessage() . " in " . $exception->getFile() . ":" . $exception->getLine() . "\n" . $exception->getTraceAsString();
        error_log($logMessage);

        // 错误显示控制
        if (ini_get('display_errors') && $this->config['debug']) {
            // 开发模式下显示有限的错误信息
            $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            $file    = htmlspecialchars(basename($exception->getFile()), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            $line    = (int)$exception->getLine();
            echo "<h1>应用运行错误</h1><p>错误信息: {$message}</p>";
            echo "<p>位置: {$file}:{$line}</p>";
        } else {
            // 生产环境显示通用错误信息
            echo "<h1>服务器内部错误</h1><p>请稍后重试，或联系管理员。</p>";
        }
    }
}
